﻿package net.nicoptere.data 
{
	
	import flash.events.Event;
	import flash.events.ErrorEvent;
	import flash.events.HTTPStatusEvent;
	import flash.events.IOErrorEvent;
	import flash.events.SecurityErrorEvent;
	import flash.events.ProgressEvent;
	import flash.events.EventDispatcher;
	import flash.events.IEventDispatcher;
	
	import flash.net.URLLoader;
	import flash.net.URLLoaderDataFormat;
	import flash.net.URLRequest;
	import flash.net.URLLoaderDataFormat;
	
	
	import net.nicoptere.utils.StringUtil;
	import net.nicoptere.utils.ArrayUtil;
	
	/**
	 * @author nicolas barradeau
	 */
	dynamic public class XMLParser extends EventDispatcher
	{
		public var xmlResults:XML;
		public var percentLoaded:Number;
		protected var xmlLoader:URLLoader
		public var debug:Boolean = false;
		
		
		public function XMLParser( url:String = '' ) 
		{
			if ( url != '' ) load( url );
		}
		
		
		public function load( configUrl:String ):void
		{
			if( debug ) trace( 'Config.load' );
			var req:URLRequest = new URLRequest(configUrl);
			
			xmlLoader = new URLLoader();
			xmlLoader.dataFormat = URLLoaderDataFormat.TEXT;
			
			percentLoaded = 0;
			configureListeners( xmlLoader );
			xmlLoader.load( req  );
			
		}
		
		//configureListeners
		protected function configureListeners(dispatcher:IEventDispatcher):void
		{
            
			if ( debug ) trace( 'Config.configureListeners' );
			dispatcher.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
            dispatcher.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
            dispatcher.addEventListener(ErrorEvent.ERROR, errorHandler);
            dispatcher.addEventListener(Event.COMPLETE, completeHandler);
            dispatcher.addEventListener(ProgressEvent.PROGRESS, progressHandler );
			
        }
		
		//removeListeners
		protected function removeListeners(dispatcher:IEventDispatcher):void
		{
            
			if( debug ) trace( 'Config.removeListeners' );
			dispatcher.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);
            dispatcher.removeEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
            dispatcher.removeEventListener(ErrorEvent.ERROR, errorHandler);
            dispatcher.removeEventListener(Event.COMPLETE, completeHandler );
            dispatcher.removeEventListener(ProgressEvent.PROGRESS, progressHandler );
			
        }
		
		/*
		*     Listener ERROR HANDLER
		*/
        protected function securityErrorHandler(event:SecurityErrorEvent):void
		{
			if( debug ) trace('Config.SecurityError');
			dispatchEvent( new ErrorEvent( ErrorEvent.ERROR ) );
			removeListeners( event.target as IEventDispatcher );
		}
		protected function httpStatusHandler(event:HTTPStatusEvent):void
		{
			if( debug ) trace('Config.HTTPerror');
			dispatchEvent( new ErrorEvent( ErrorEvent.ERROR ) );
			removeListeners( event.target as IEventDispatcher );
		}
		protected function ioErrorHandler(event:IOErrorEvent):void
		{
			if( debug ) trace('Config.IOerror', event.toString());
			dispatchEvent( new ErrorEvent( ErrorEvent.ERROR ) );
			removeListeners( event.target as IEventDispatcher );
		}
		protected function errorHandler(event:IOErrorEvent):void
		{
			if( debug ) trace('Config.error');
			dispatchEvent( new ErrorEvent( ErrorEvent.ERROR ) );
			removeListeners( event.target as IEventDispatcher );
		}
		
		/**
		 * XML file is loading
		 * @param	event
		 */
		protected function progressHandler(event:ProgressEvent ):void
		{
			if ( debug ) trace( 'Config.progress', event.bytesLoaded, event.bytesTotal,( event.bytesLoaded / event.bytesTotal ) );
			percentLoaded = ( event.bytesLoaded / event.bytesTotal );
        }
		
		/**
		 * loading is complete
		 * @param	event
		 */
		protected function completeHandler(event:Event):void
		{
			removeListeners( event.target as IEventDispatcher );
		    var loader:URLLoader = URLLoader(event.target);
			var result:XML = new XML (loader.data);
			XML.ignoreWhitespace = true;
			parseXML( result );
			
		}
		
		
		/**
		 * parsing and creating variables from the XML file
		 * @param	xml
		 */
		protected function parseXML( xml:XML ):void 
		{
			
			var ns:Namespace = new Namespace( xml.namespace() );
			//default xml namespace = ns;
			xmlResults = xml;
			
			
			var i:int, j:int;
			var obj:Object;
			var node:XMLList;
			
			var name:String;
			var element:String;
			
			
			for ( i = 0; i < xml.children().length(); i++ )
			{
				node = xmlResults.child( i );
				name = StringUtil.removeSpecialChars( String( node.name() ).toLowerCase() );
				
				if (debug )trace( 'parseXml: block ->', i, '->', name, ' numchildren',  node.children().length() )
				
				obj = new Object();
				obj.name = name;
				
				//entry already exists replace it with an array
				if ( this[ name ] != null )
				{
					if ( !( this[ name ] is Array ) )
					{
						var tmp:Object = this[ name ];
						this[ name ] = new Array();
						this[ name ].push( tmp );
					}
					this[ name ].push( obj );
				}
				else 
				{
					this[ obj.name ] = obj;
				}
				
				//if the node has children
				if ( node.children().length() > 0 ) 
				{
					recursiveParse( node, obj );
				}
				
				//if the node has attributes
				if ( node.attributes().length() > 0 )
				{
					for ( j = 0; j < node.attributes().length(); j++ )
					{
						//trace( node.attributes()[ j ] );
						obj[ StringUtil.removeSpecialChars( node.attributes()[ j ].name() ).toLowerCase() ] = node.attributes()[ j ];
					}
				}
			}
			
			if ( debug ) trace( 'XMLParser dispatch COMPLETE' );
			dispatchEvent( new Event( Event.COMPLETE ) );
			
		}
		
		protected function recursiveParse( xml:XMLList, prop:Object ):void
		{
			
			var obj:Object;
			var node:XMLList;
			var name:String;
			
			var i:int, j:int;
			for ( i = 0; i < xml.children().length(); i++ )
			{
				//for each node
				
				node = xml.child( i );
				name = StringUtil.removeSpecialChars( String( node.name() ).toLowerCase() );
				
				if ( debug && node.name() != null ) trace( '\trecursive ', node.name(), '-> ', i, ':', name, 'numChildren', node.children().length() );
				
				obj = new Object();
				obj.name = name;
				
				//entry already exists replace it with an array
				if ( prop[ name ] != null )
				{
					if ( !( prop[ name ] is Array ) )
					{
						var tmp:Object = prop[ name ];
						prop[ name ] = new Array();
						prop[ name ].push( tmp );
					}
					prop[ name ].push( obj );
				}
				else 
				{
					prop[ obj.name ] = obj;
				}
				
				//if the node has children
				if ( node.children().length() > 0 ) 
				{
					recursiveParse( node, obj );
				}
				
				//if the node has attributes
				if ( node.attributes().length() > 0 )
				{
					var att:Object = new Object();
					att.name = StringUtil.removeSpecialChars( node.name() ).toLowerCase();
					
					att.value = node;
					att.toString = function():String { return this.value.toString(); };
					
					for ( j = 0; j < node.attributes().length(); j++ )
					{
						
						name = StringUtil.removeSpecialChars( node.attributes()[ j ].name() ).toLowerCase();
						att[ name ] = new Object();
						att[ name ].value = node.attributes()[ j ];
						att[ name ].toString = function():String { return this.value; };
						
					}
					prop[ att.name ] = att;
				}
				
				//finish
				if( node.name() == null )
				{
					
					prop.value = getType( node );
					
					prop.toString = function():String { return this.value.toString(); } 
				}
			}
		}
		protected function getType( node:String ):*
		{
			
			if ( StringUtil.removespaces( node ) ) return node
			return node;
		}
	}
}